package com.ht.api.db;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.sql.DataSource;

import com.ht.api.db.bean.Column;
import com.ht.api.db.bean.Table;
import com.ht.api.db.constant.CacheConstants;
import com.ht.api.db.exception.HtException;
import com.ht.api.db.util.RedisUtil;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;

/**
 * 数据库元数据操作
 * @author asq
 * @createTime 2024年11月29日 23:21:59
 */
@Slf4j
public class DbConfig {
	/** 系统数据源 */
	private static DataSource sysDS = SpringUtil.getBean(DataSource.class);
	/** 租户数据源 */
	public static Map<Long, DataSource> tenantDSMap = new HashMap<>();
	/** 系统数据源的StreamLoad配置 */
	private static StreamLoadConfig sysStreamLoadConfig = SpringUtil.getBean(StreamLoadConfig.class);;
	/** 租户数据源的StreamLoad配置 */
	public static Map<Long, StreamLoadConfig> tenantStreamLoadConfigMap = new HashMap<>();

	/**
	 * 获取租户数据源
	 */
	public static DataSource useTenantDb(long tenantId) {
		return tenantDSMap.get(tenantId);
	}

	/**
	 * 获取系统数据源
	 */
	public static DataSource useSysDb() {
		return sysDS;
	}
	
	/**
	 * 获取租户数据源的StreamLoad配置
	 * @return
	 */
	public static StreamLoadConfig useTenantSLC(long tenantId) {
		return tenantStreamLoadConfigMap.get(tenantId);
	}

	/**
	 * 获取系统数据源的StreamLoad配置
	 */
	public static StreamLoadConfig useSysSLC() {
		return sysStreamLoadConfig;
	}
	
	/**
	 * 根据jdbc数据源，获取对应的StreamLoad配置
	 * @param dataSource jdbc数据源
	 * @return
	 */
	public static StreamLoadConfig getStreamLoadConfigByDataSource(DataSource dataSource) {
		// 在jdbc数据源列表中，查找对应的租户ID
		long tenantId = 0;
		for (Long key : tenantDSMap.keySet()) {
			if (tenantDSMap.get(key) == dataSource) {
				tenantId = key;
			}
		}
		
		// 根据租户ID，返回对应的StreamLoadConfig配置
		return tenantStreamLoadConfigMap.get(tenantId);
	}
	
	/**
	 * 根据StreamLoad配置，获取对应的jdbc数据源
	 * @param config StreamLoad配置
	 * @return
	 */
	public static DataSource getDataSourceByStreamLoadConfig(StreamLoadConfig config) {
		// 在jdbc数据源列表中，查找对应的租户ID
		long tenantId = 0;
		for (Long key : tenantStreamLoadConfigMap.keySet()) {
			if (tenantStreamLoadConfigMap.get(key) == config) {
				tenantId = key;
			}
		}
		
		// 根据租户ID，返回对应的jdbc数据源
		return tenantDSMap.get(tenantId);
	}
	
	/**
	 * 根据jdbc数据源，获取对应的租户ID
	 * @param dataSource jdbc数据源
	 * @return
	 */
	public static long getTenantId(DataSource dataSource) {
		// 在jdbc数据源列表中，查找对应的租户ID
		long tenantId = 0;
		for (Long key : tenantDSMap.keySet()) {
			if (tenantDSMap.get(key) == dataSource) {
				tenantId = key;
			}
		}
		
		return tenantId;
	}
	
	/**
	 * 加载租户的jdbc数据源
	 * @param tenantId 租户ID
	 * @param tenantConfig 租户数据源配置
	 * @return
	 */
	public static DataSource loadTenantDs(long tenantId, JSONObject tenantConfig) {
		try {
			HikariConfig config = new HikariConfig();

			// 基本配置
			config.setJdbcUrl(tenantConfig.getStr("url"));
			config.setUsername(tenantConfig.getStr("username"));
			config.setPassword(tenantConfig.getStr("password"));
			config.setDriverClassName("com.mysql.cj.jdbc.Driver");

			// 连接池配置
			config.setMaximumPoolSize(20);
			config.setMinimumIdle(10);
			config.setIdleTimeout(600000);
			config.setConnectionTimeout(30000);
			config.setMaxLifetime(1800000);

			// 连接测试
			config.setConnectionTestQuery("SELECT 1");
			config.setValidationTimeout(5000);

			// 存入本地map
			DataSource tenantDs = new HikariDataSource(config);
			tenantDSMap.put(tenantId, tenantDs);
			
			return tenantDs;
		} catch (Exception e) {
			log.error("初始化租户数据源失败", e);
			throw new RuntimeException("初始化租户数据源失败", e);
		}
	}

	/**
	 * 加载租户的StreamLoad数据源
	 * @param tenantId 租户ID
	 * @param tenantStreamLoadConfig 租户StreamLoad数据源配置
	 * @return
	 */
	public static StreamLoadConfig loadTenantSLC(long tenantId, JSONObject tenantConfig) {
		try {
			// 解析租户配置
			String jdbcUrl = tenantConfig.getStr("url");
			// 示例URL: jdbc:mysql://host:9030/dbname?param=value
			String urlWithoutPrefix = jdbcUrl.substring(jdbcUrl.indexOf("://") + 3);
			String hostPort = urlWithoutPrefix.substring(0, urlWithoutPrefix.indexOf("/"));
			String[] hostPortArr = hostPort.split(":");

			// 获取数据库名
			String dbName = urlWithoutPrefix.substring(urlWithoutPrefix.indexOf("/") + 1);
			if (dbName.contains("?")) {
				dbName = dbName.substring(0, dbName.indexOf("?"));
			}
			
			// 创建StreamLoad的配置对象
			StreamLoadConfig tenantStreamLoadConfig = new StreamLoadConfig();
			tenantStreamLoadConfig.setFeHost(hostPortArr[0]);
			tenantStreamLoadConfig.setDbName(dbName);
			tenantStreamLoadConfig.setUsername(tenantConfig.getStr("username"));
			tenantStreamLoadConfig.setPassword(tenantConfig.getStr("password"));

			// 保存进缓存
			tenantStreamLoadConfigMap.put(tenantId, tenantStreamLoadConfig);
			return tenantStreamLoadConfig;
		} catch (Exception e) {
			log.error("获取租户数据源的StreamLoad配置失败", e);
			throw new RuntimeException("获取租户数据源的StreamLoad配置失败", e);
		}
	}

	/**
	 * 刷新表字段缓存tableMap
	 * @param tableCode
	 */
	public static Table refreshTableMap(String tableCode) {
		log.info("刷新表字段缓存tableMap，表名：{}", tableCode);
		Table table = getColumnListByDb(tableCode);
		String key = getKey(tableCode);
		if (table != null) {
			RedisUtil.set(key, table);
		} else {
			RedisUtil.delete(key);
		}
		return table;
	}

	/**
	 * 从缓存获取指定表的字段列表
	 * @param tableCode
	 * @return
	 */
	public static List<Column> columnList(String tableCode) {
		return getTable(tableCode).getColumns();
	}

	/**
	 * 根据表名及字段ID，获取字段名
	 * @param tableCode
	 * @return
	 */
	public static String getColumnCode(String tableCode, String columnId) {
		List<Column> columnList = columnList(tableCode);
		for (Column column : columnList) {
			if (column.getId().equals(columnId)) {
				return column.getCode();
			}
		}
		throw new HtException("根据字段ID获取字段code失败，ID不存在：" + columnId);
	}

	/**
	 * 从缓存获取指定表的字段列表
	 * @param tableCode
	 * @return
	 */
	public static String getColumns(String tableCode) {
		String columns = columnList(tableCode).stream().map(c -> {
			String column = String.format("`%s`", c.getCode());
			if ("bitmap".equals(c.getType())) {
				return String.format("%s,%s=ifnull(bitmap_from_string(%s),bitmap_empty())", column, column, column);
			} else {
				return column;
			}
		}).collect(Collectors.joining(","));
		return columns;
	}
	
	/**
	 * 判断一个列名是否存在
	 * @param tableCode 表名
	 * @param column 列名
	 * @return
	 */
	public static boolean exists(String tableCode, String column) {
		List<Column> list = columnList(tableCode);
		for (Column col : list) {
			if (col.getCode().equals(column)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 判断是否有自然主键
	 * @param tableCode
	 * @return
	 */
	public static boolean isNaturePrimary(String tableCode) {
		return !getTable(tableCode).getPrimaryKeys().isEmpty();
	}

	/**
	 * 获取指定表的主键列表
	 * @param tableCode
	 * @return
	 */
	public static List<String> getPrimaryKeys(String tableCode) {
		return getTable(tableCode).getPrimaryKeys();
	}
	
	/**
	 * 根据表的ID获取真实表名
	 * @param mainId
	 * @return
	 */
	public static String getTableCode(long mainId) {
		DbList dblist = RedisUtil.get(CacheConstants.KEY_TABLE_LIST);
		
		for (Entity entity : dblist) {
			long entityMainId = entity.getLong("main_id");
			if (mainId == entityMainId) {
				return entity.getStr("code");
			}
		}
		
		throw new HtException(String.format("表不存在：[%s]", mainId));
	}
	
	/**
	 * 根据表的别名获取真实表名
	 * @param alias
	 * @return
	 */
	public static String getAlias(String alias) {
		List<Entity> dblist = RedisUtil.get(CacheConstants.KEY_TABLE_LIST);
		
		for (Entity entity : dblist) {
			String entityAlias = entity.getStr("alias");
			if (alias.equals(entityAlias)) {
				return entity.getStr("code");
			}
		}
		
		throw new HtException(String.format("表不存在：[%s]", alias));
	}
	
	/**
	 * 是否是租户表
	 * @param tableCode 表名
	 * @return
	 */
	public static boolean isTenant(String tableCode) {
		Table table = getTable(tableCode);
		if (table == null) {
			throw new HtException(String.format("表不存在：[%s]", tableCode));
		}
		return table.isTenant();
	}
	
	/**
	 * 根据指定表名，返回datetime类型的字段名称列表，以便进行格式化
	 * @param table
	 * @return
	 */
	public static List<String> getDateTimeColumns(String table) {
		List<Column> columns = columnList(table);
		return columns.stream().filter(c -> "datetime".equals(c.getType())).map(Column::getCode).toList();
	}

	/**
	 * 根据表名称获取表元数据
	 */
	public static Table getTable(String tableCode) {
		return RedisUtil.get(getKey(tableCode));
	}

	/**
	 * 从数据库获取指定表的字段列表
	 * @param tableCode
	 * @return
	 */
	private static Table getColumnListByDb(String tableCode) {
		Table table = new Table();
		try {
			// 查询table
			Entity deTable = Db.createSqlParser("db_table").where("code", tableCode).findOne();
			if (deTable == null) {
				return null;
			}
			table.setCode(tableCode)
				.setMainId(deTable.getLong("main_id"))
				.setName(deTable.getStr("name"))
				.setAlias(deTable.getStr("alias"))
				.setTenant(deTable.getBool("tenant"))
				.setDelConfig(deTable.getStr("del_config"));

			// 查询column列表
			String primaryKey = "primary_key";
			DbList columnList = Db.createSqlParser("db_column").where("table_code", tableCode).query();
			columnList.stream().forEach(c -> {
				Column column = new Column();
				column.setId(c.getStr("main_id"));
				column.setTable(tableCode);
				column.setCode(c.getStr("code"));
				column.setName(c.getStr("name"));
				column.setType(c.getStr("type"));
				column.setFun(c.getStr("fun"));
				column.setDefaultValue(c.getStr("default_value"));
				column.setPrimaryKey(c.getInt(primaryKey));
				column.setDict(c.getLong("dict_id"));
				column.setMapperConfig(c.getStr("mapper_config"));

				table.getColumns().add(column);
			});

			table.getPrimaryKeys().addAll(columnList.stream().filter(c -> {
				if (c.containsKey(primaryKey) && c.getObj(primaryKey) != null) {
					return c.getInt(primaryKey) != null && c.getInt(primaryKey) > 0;
				}
				return false;
			}).sorted((c1, c2) -> {
				return c1.getInt(primaryKey) - c2.getInt(primaryKey);
			}).map(c -> c.getStr("code")).toList());

		} catch (Exception e) {
			log.error("[db]提取表结构出现异常：" + e.getMessage(), e);
		}
		return table;
	}
	
	/**
	 * 生成redis中的key
	 * @param table 表名
	 * @return
	 */
	private static String getKey(String table) {
		return CacheConstants.KEY_TABLE + table;
	}
}
